mod gather;
mod timer;
mod trigger;
mod util;

use super::{
    json, Client, MapSv, Plc, RNull, SvCache, VBase, VMap, Value, MSI, ST_ACTIVE, ST_DOING,
    ST_DONE, ST_WAIT,
};
use std::time::Duration;
use tokio::time::sleep;
use tracing::{instrument, warn};

pub use util::{get_state, is_active};

#[instrument(skip_all, name = "do_task")]
pub async fn do_task(cfg: SvCache, real: SvCache, client: &Client) {
    // task id
    // "ta.<plc>.<it>": 0
    let mut task = MSI::new();
    loop {
        let _ = ck_task(&cfg, &real, client, &mut task).await;

        sleep(Duration::from_millis(10)).await;
    }
}

#[instrument(skip_all, name = "ck_task")]
async fn ck_task(cfg: &SvCache, real: &SvCache, client: &Client, task: &mut MSI) -> Option<()> {
    // the iterface's cache id list
    let iter = cfg.get("it")?;
    let iter = iter.array()?;

    let now = base::cur_time_timestamp();

    for cache_id in iter.iter() {
        let cache_id = cache_id.kstr()?;

        let it = cfg.get(cache_id)?;
        let it = it.as_object()?;

        if !is_active(it, "active") {
            continue;
        }

        let plc_id = it.k_str("plc")?;

        let plc = match cfg.get(plc_id) {
            Some(v) => v,
            None => {
                continue;
            }
        };
        let plc = plc.as_object()?;

        if let None = is_task_hit(cache_id, it, now, task) {
            continue;
        }

        let st_id = format!("st.{cache_id}");

        if let Some(_) = is_task_doing(&st_id, &real) {
            continue;
        }

        let plc = Plc::new(it, plc, &client).ok()?;

        if let Err(e) = plc.connect().await {
            warn!(target: "PLC连接失败", "plc connect error: {:?}", e);
            continue;
        }

        let c_real = real.clone();

        tokio::spawn(async move {
            if plc.is_trigger() {
                if let Err(e) = trigger::scan_trigger(plc, c_real, st_id).await {
                    warn!(target: "读取信号失败", "scan_trigger error: {:?}", e);
                }
            } else if plc.is_timer() {
                if let Err(e) = timer::scan_timer(plc, c_real, st_id).await {
                    warn!(target: "读取信号失败", "scan_timer error: {:?}", e);
                }
            }
        });
    } // for

    Some(())
}

fn is_task_hit(cache_id: &str, it: &MapSv, now: i64, task: &mut MSI) -> Option<()> {
    let scan_time = it.k_i64("time")?;
    let task_id = format!("ta.{cache_id}");

    match task.get(&task_id) {
        Some(cur) => {
            let sub = now - cur;

            if sub >= scan_time {
                task.insert(task_id, now);
                return Some(());
            }
        }
        None => {
            task.insert(task_id, now);
            return Some(());
        }
    }

    None
}

fn is_task_doing(st_id: &str, cache: &SvCache) -> Option<()> {
    let state = get_state(&st_id, &cache);

    if state == ST_DOING || state == ST_ACTIVE {
        return Some(());
    }

    None
}
